﻿using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System;
using System.Collections.Specialized;
#if WPF
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Markup;
using System.Windows.Media;
#else
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.Foundation;
using Windows.UI.Xaml.Media;
using Windows.UI;
#endif

namespace Eternal.DataVisualization
{
    /// <summary>
    /// Chart
    /// </summary>
    public abstract class Chart : Control
    {
        ContentPresenter headerPresenter;

        public string Header
        {
            get { return (string)GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Header.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeaderProperty =
            DependencyProperty.Register("Header", typeof(string), typeof(Chart), new PropertyMetadata(""));

        public DataTemplate HeaderTemplate
        {
            get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
            set { SetValue(HeaderTemplateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HeaderTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeaderTemplateProperty =
            DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(Chart), new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets the area.
        /// </summary>
        /// <value>
        /// The area.
        /// </value>
        internal Border Area { get; set; }

        /// <summary>
        /// The template applied
        /// </summary>
        bool templateApplied, measured;

        /// <summary>
        /// The SBS series
        /// </summary>
        internal List<DepthType> depthSeries;

        /// <summary>
        /// Gets or sets the chart axis root panel.
        /// </summary>
        /// <value>
        /// The chart axis root panel.
        /// </value>
        internal Grid ChartRootPanel { get; set; }
        /// <summary>
        /// Gets or sets the size of the available.
        /// </summary>
        /// <value>
        /// The size of the available.
        /// </value>
        internal Size AvailableSize { get; set; }
        /// <summary>
        /// Gets or sets the size of the area.
        /// </summary>
        /// <value>
        /// The size of the area.
        /// </value>
        public Size AreaSize { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether [show legend].
        /// </summary>
        /// <value><c>true</c> if [show legend]; otherwise, <c>false</c>.</value>
        public bool ShowLegend
        {
            get { return (bool)GetValue(ShowLegendProperty); }
            set { SetValue(ShowLegendProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShowLegend.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowLegendProperty =
            DependencyProperty.Register("ShowLegend", typeof(bool), typeof(Chart), new PropertyMetadata(false));

        /// <summary>
        /// Gets or sets the legend.
        /// </summary>
        /// <value>
        /// The legend.
        /// </value>
        internal Legend Legend
        {
            get { return (Legend)GetValue(LegendProperty); }
            set { SetValue(LegendProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Legend.  This enables animation, styling, binding, etc...
        /// <summary>
        /// The legend property
        /// </summary>
        public static readonly DependencyProperty LegendProperty =
            DependencyProperty.Register("Legend", typeof(Legend), typeof(Chart), new PropertyMetadata(null));

       

        /// <summary>
        /// Initializes a new instance of the <see cref="Chart"/> class.
        /// </summary>
        public Chart()
        {
            DefaultStyleKey = typeof(Chart);
            Loaded += Chart_Loaded;

            depthSeries = new List<DepthType>();

            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 157, 247, 29)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 13, 139, 201)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 199, 210, 41)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 213, 136, 12)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 56, 132, 56)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 48, 198, 196)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 208, 83, 26)));
            Brushes.Add(new SolidColorBrush(Color.FromArgb(255, 179, 153, 71)));
        }

        /// <summary>
        /// Handles the CollectionChanged event of the Seriess control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        private void Seriess_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
           
        }
      

        /// <summary>
        /// Handles the Loaded event of the Chart control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        void Chart_Loaded(object sender, RoutedEventArgs e)
        {
            //SetValuesForAxis();
            //YAxis.Type = Series[0].YDataType;
            //YAxis.InvalidateMeasure();
            //YAxis.CanUpdate = true;
            //XAxis.CanUpdate = true;
            //XAxis.Type = Series[0].XDataType;
            //foreach (Series Series in Series)
            //{
            //    Series.canUpdate = true;
            //}
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate" />.
        /// </summary>
#if WPF
        public override void OnApplyTemplate()
#else
        protected override void OnApplyTemplate()
#endif
        {
            ChartRootPanel = GetTemplateChild("PART_RootPanel") as Grid;
            Area = GetTemplateChild("PART_Area") as Border;
            headerPresenter = GetTemplateChild("PART_Header") as ContentPresenter;
            PlaceElement();
            templateApplied = true;
            Invalidate();
            if (ShowLegend)
                Legend = new Legend();
            base.OnApplyTemplate();
        }

        bool alreadyDispatched;

        /// <summary>
        /// Dynamics the invalidate.
        /// </summary>
        internal void DynamicInvalidate()
        {
            if (!alreadyDispatched)
#if WPF_UN
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(Invalidate));
#endif

#if SILVERLIGHT
                Dispatcher.BeginInvoke(new Action(Invalidate));
#endif

#if NETFX_CORE
                Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, Invalidate);
#endif
            alreadyDispatched = true;
        }

        /// <summary>
        /// Invalidates this instance.
        /// </summary>
        internal void Invalidate()
        {
            alreadyDispatched = false;
            
            if (!measured || !templateApplied) return;
            var axisChart = this as CartesianChart;
            if (axisChart != null)
            {
                foreach (var series in UISeries)
                {
                    series.XAxis = axisChart.XAxis;
                    series.YAxis = axisChart.YAxis;
                    series.InvalidateData();
                }
            }
            else
            {
                foreach (var series in UISeries)
                {
                    series.InvalidateData();
                }
            }

            UpdateUI();
        }

        bool alreadyRenderDispatched;

        /// <summary>
        /// Dynamics the invalidate.
        /// </summary>
        internal void DynamicUIUpdate()
        {
            if (!alreadyRenderDispatched)
#if WPF_UN
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(UpdateUI));
#endif
#if SILVERLIGHT
                Dispatcher.BeginInvoke(new Action(UpdateUI));
#endif
#if NETFX_CORE
            Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, UpdateUI);
#endif
            alreadyRenderDispatched = true;
        }

        internal void UpdateUI()
        {
            alreadyRenderDispatched = false;
            var axisSeries = this as CartesianChart;
            if (axisSeries != null)
            {
                foreach (var axis in axisSeries.VisibleAxis)
                {
                    axis.DynamicUpdateVisual();
                    axis.GridLinePanel.PositionGridLines();
                }
            }

            foreach (var series in UISeries.Where(series => series.PointsGenerated))
            {
                series.DrawSegment();
            }
        }

        public SeriesCollection UISeries { get; set; }

        /// <summary>
        /// Places the element.
        /// </summary>
        internal void PlaceElement()
        {
            if (Legend != null)
            {
                Legend.Chart = this;
                ChartRootPanel.Children.Add(Legend);
                Legend.HorizontalAlignment = HorizontalAlignment.Center;
                Grid.SetColumn(Legend, 1);
                Grid.SetRow(Legend, 1);
            }
            headerPresenter.Content = Header;
            headerPresenter.ContentTemplate = HeaderTemplate;

            var areaGrid = new Grid();
            Area.Child = areaGrid;

            foreach (var series in UISeries)
            {
                series.Area = this;
                areaGrid.Children.Add(series);
            }
            var axisChart = this as CartesianChart;
            if (axisChart != null)
            {
                axisChart.VisibleAxis = new List<Axis>();
                if (UISeries.Count > 0 && !(UISeries[0] is NonAxisType))
                {
                    Grid.SetColumn(axisChart.YAxis, 0);
                    Grid.SetRow(axisChart.YAxis, 3);
                    Grid.SetColumn(axisChart.XAxis, 1);
                    Grid.SetRow(axisChart.XAxis, 4);
                    axisChart.VisibleAxis.Add(axisChart.XAxis);
                    axisChart.VisibleAxis.Add(axisChart.YAxis);

                    foreach (var axis in axisChart.VisibleAxis)
                    {
                        ChartRootPanel.Children.Add(axis);
                    }
                }

                foreach (var axis in axisChart.VisibleAxis)
                {
                    var gridLines = new GridLinesPanel(axis);
                    axis.GridLinePanel = gridLines;
                    areaGrid.Children.Add(gridLines);
                }
            }
        }

        /// <summary>
        /// Values to coordiante X axis.
        /// </summary>
        /// <param name="maximum"></param>
        /// <param name="value">The value.</param>
        /// <param name="distance">The distance.</param>
        /// <param name="minimum"></param>
        /// <returns></returns>
        public double ValueToCoordianteXAxis(double minimum, double maximum, double value, double distance)
        {
            return Math.Round(distance * (value - minimum) / (maximum - minimum));
        }

        /// <summary>
        /// Values to coordiante Y axis.
        /// </summary>
        /// <param name="maximum"></param>
        /// <param name="value">The value.</param>
        /// <param name="distance">The distance.</param>
        /// <param name="minimum"></param>
        /// <returns></returns>
        public double ValueToCoordianteYAxis(double minimum, double maximum, double value, double distance)
        {
            return Math.Round(distance * (1 - (value - minimum) / (maximum - minimum)));
        }

        //public Point ValueToCoordiante(Point value)
        //{
        //    return new Point(Math.Round(AvailableWidth * (value.X - XMinimum) / (XMaximum - XMinimum)),
        //                   Math.Round(AvailableHeight * (1 - (value.Y - YMinimum) / (YMaximum - YMinimum))));
        //}

        /// <summary>
        /// Called to remeasure a control.
        /// </summary>
        /// <param name="constraint">The maximum size that the method can return.</param>
        /// <returns>
        /// The size of the control, up to the maximum specified by <paramref name="constraint" />.
        /// </returns>
        protected override Size MeasureOverride(Size constraint)
        {
            var size = base.MeasureOverride(constraint);
            AvailableSize = constraint;
            measured = true;
            DynamicUIUpdate();
            if (Legend != null)
                Legend.UpdateLegned(constraint);
            return size;
        }

        /// <summary>
        /// Renders the grid lines.
        /// </summary>
        internal void RenderGridLines()
        {

        }

        /// <summary>
        /// Gets the segment distance.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <returns></returns>
        internal SegmentDistance GetSegmentDepth(DepthType series)
        {
            var segmentDistance = new SegmentDistance();

            var count = depthSeries.Count;
            var index = depthSeries.IndexOf(series);
            var distance = 0.8 / count;
            segmentDistance.Start = Math.Round((distance * index - 0.8 / 2), 5);
            segmentDistance.End = Math.Round((segmentDistance.Start + distance), 5);
            segmentDistance.Delta = Math.Round(segmentDistance.End - segmentDistance.Start, 5);
            return segmentDistance;
        }

        /// <summary>
        /// The initial point
        /// </summary>
        Point initialPoint;

#if WPF
        bool canPan;
        /// <summary>
        /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonDown" /> routed event is raised on this element. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. The event data reports that the left mouse button was pressed.</param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            CaptureMouse();
            canPan = true;
            initialPoint = e.GetPosition(this);
            base.OnMouseLeftButtonDown(e);
        }

        /// <summary>
        /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonUp" /> routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. The event data reports that the left mouse button was released.</param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            ReleaseMouseCapture();
            canPan = false;
            base.OnMouseLeftButtonUp(e);
        }

#else
        protected override void OnPointerPressed(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            CapturePointer(e.Pointer);
            initialPoint = e.GetCurrentPoint(this).Position;
            base.OnPointerPressed(e);
        }

        protected override void OnPointerReleased(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            ReleasePointerCapture(e.Pointer);
            base.OnPointerReleased(e);
        }
#endif


        /// <summary>
        /// Invoked when an unhandled <see>
        ///     <cref>E:System.Windows.Input.Mouse.MouseMove</cref>
        /// </see>
        ///     attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseEventArgs" /> that contains the event data.</param>
#if WPF
        protected override void OnMouseMove(MouseEventArgs e)
        {
            var point = e.GetPosition(this);
           
            if(canPan)
#else
        protected override void OnPointerMoved(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
         {
            var pointer = e.GetCurrentPoint(this);
            var point = pointer.Position;
            if (pointer.Properties.IsXButton1Pressed)
#endif
            {
                var diffX = point.X - initialPoint.X;
                var diffY = point.Y - initialPoint.Y;
                if (ChartPaning(diffX, diffY))
                {
                    Invalidate();
                }
                
            }

            initialPoint = point;
#if WPF
            base.OnMouseMove(e);
#else
            base.OnPointerMoved(e);
#endif
        }

#if WPF

        protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
        {
         
            if (ChartPaning(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y))
            {
                Invalidate();
            }
            base.OnManipulationDelta(e);
        }

#endif

        readonly List<Brush> Brushes = new List<Brush>();

        internal Brush GetBrush(int index)
        {
            return Brushes[index % 8];
        }
        
        private bool ChartPaning(double diffX, double diffY)
        {
            var update = false;
            var axisType = this as CartesianChart;
            if (axisType != null)
            {
                foreach (var axis in axisType.VisibleAxis)
                {
                    if (axis.AllowZoomPan)
                    {
                        update = true;
                        if (axis is XAxis && diffX != 0d)
                        {
                            var zoomOffset = axis.ZoomOffset - ((diffX / axis.ActualWidth) / 2);
                            axis.ZoomOffset = zoomOffset < 0 ? 0 : zoomOffset > 1 ? 1 : zoomOffset;
                        }
                        else if (axis is YAxis && diffY != 0d)
                        {
                            var zoomOffset = axis.ZoomOffset + ((diffY / axis.ActualHeight) / 2);
                            axis.ZoomOffset = zoomOffset < 0 ? 0 : zoomOffset > 1 ? 1 : zoomOffset;
                        }
                    }
                }
            }
            return update;
        }

        /// <summary>
        /// Invoked when an unhandled <see /> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseWheelEventArgs" /> that contains the event data.</param>
#if WPF
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
         var isZoomIn = e.Delta > 0;
#else
        protected override void OnPointerWheelChanged(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
         var isZoomIn = e.GetCurrentPoint(this).Properties.MouseWheelDelta > 0;   
#endif

            var update = false;
            var axisChart = this as CartesianChart;
            if (axisChart != null)
            {
                foreach (var axis in axisChart.VisibleAxis.Where(axis => axis.AllowZoomPan))
                {
                    update = true;
                    var zoomCoefficient = !isZoomIn ? Math.Min(axis.ZoomCoefficient + 0.05, 1) : Math.Max(axis.ZoomCoefficient - 0.05, axis.MinZoomCoefficient);

                    var diff = axis.ZoomCoefficient - zoomCoefficient;
                    axis.ZoomCoefficient = zoomCoefficient;

                    axis.ZoomOffset = axis.ZoomOffset + diff * 0.5;
                }
            }

            if (update)
                Invalidate();
#if WPF
            base.OnMouseWheel(e);
#else
            base.OnPointerWheelChanged(e);
#endif
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class SegmentDistance
    {
        /// <summary>
        /// Gets or sets the start.
        /// </summary>
        /// <value>
        /// The start.
        /// </value>
        public double Start { get; set; }
        /// <summary>
        /// Gets or sets the end.
        /// </summary>
        /// <value>
        /// The end.
        /// </value>
        public double End { get; set; }
        /// <summary>
        /// Gets or sets the delta.
        /// </summary>
        /// <value>
        /// The delta.
        /// </value>
        public double Delta { get; set; }
    }
}
